package com.aboverock.integrations.redis.lock;

import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * <p>
 * Redis分布式锁工具类
 * <p>
 *
 * @author RockerWang
 * @date Created in 2021/12/3 0:44
 */
@Component
@Slf4j
public class RedisLockUtil {

    /**
     * 存储到redis中的锁标志
     */
    private static final String LOCKED = "locked";

    /**
     * 默认请求超时时间(ms 毫秒)
     */
    private static final long DEFAULT_TIME_OUT = 1000;

    /**
     * 默认过期时间(ms 毫秒)
     */
    private static final int DEFAULT_EXPIRE = 60 * 1000;

    private static StringRedisTemplate redisTemplate;

    @Autowired
    public RedisLockUtil(StringRedisTemplate redis) {
        redisTemplate = redis;
    }

    /**
     * 加锁 默认过期时间 60s
     *
     * @param key        redis key
     * @return true: 加锁成功 false: 加锁失败
     */
    public static boolean lock(String key) {
        return lock(key, DEFAULT_EXPIRE, DEFAULT_TIME_OUT);
    }

    /**
     * 加锁
     *
     * @param key        redis key
     * @param expireTime 过期时间(ms 毫秒)
     * @return true: 加锁成功 false: 加锁失败
     */
    public static boolean lock(String key, int expireTime) {
        return lock(key, expireTime, DEFAULT_TIME_OUT);
    }

    /**
     * 加锁
     *
     * @param key        redis key
     * @param expireTime 过期时间(ms 毫秒)
     * @param timeout    超时时间(ms 毫秒)
     * @return true: 加锁成功 false: 加锁失败
     */
    public static boolean lock(String key, int expireTime, long timeout) {
        final Random random = new Random();
        long nowTime = System.currentTimeMillis();
        boolean isLocked = false;

        // 不断循环箱redis请求锁，超过时间则放弃请求锁
        while ((System.currentTimeMillis() - nowTime) < timeout) {
            // 将锁作为key存储到redis中, 存储成功则获得锁
            if (redisTemplate.opsForValue().setIfAbsent(key, LOCKED)) {
                isLocked = true;

                // 设置锁的有效期，也是锁的自动释放时间，也是一个客户端在其他客户端能抢占锁之前可以执行任务的时间
                // 可以防止因异常情况无法释放锁而造成死锁情况的发生
                redisTemplate.expire(key, expireTime, TimeUnit.MILLISECONDS);

                // 上锁成功结束请求
                break;
            }

            // 获取锁失败时，应该在随机延时后进行重试，避免不同客户端同时重试导致谁都无法拿到锁的情况出现
            // 睡眠10毫秒后继续请求锁
            try {
                Thread.sleep(10, random.nextInt(500));
            } catch (InterruptedException e) {
                log.error("获取分布式锁休眠被中断：", e);
            }
        }

        return isLocked;
    }

    /**
     * key是否锁住
     *
     * @param key redis分布式锁key值
     * @return true: 加锁成功 false: 加锁失败
     */
    public static boolean isLock(String key) {
        Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().time();
        return Optional.ofNullable(redisTemplate.hasKey(key)).orElse(false);
    }

    /**
     * 释放锁
     *
     * @param key redis 分布式锁key值
     */
    public static void unlock(String key) {
        // 不管请求锁是否成功，只要已经上锁，客户端都会进行释放锁的操作
        redisTemplate.delete(key);
    }
}
